package lift_simulator;
import java.util.ArrayList;

public class Building {
	/**
	 * ビルに存在するエレベータオブジェクトのリスト
	 */
	private ArrayList<Lift> lifts;
	/**
	 * ビルに存在するフロアのリスト。<br>
	 * 配列の番号(get(i))と階数(実際はi+1)が違うことに注意。
	 */
	private ArrayList<Floor> floors;
	/**
	 * リフトの動作を管理するオブジェクト
	 */
	private LiftManager liftManager;
	/**
	 * GUIで現在の状態を表示するオブジェクト
	 */
	private Window window;
	
	private CsvManager csvManager;
	private Clock clock;

	/**
	 * ビルの初期化。<br>
	 * @param liftNum
	 * @param liftCapacity
	 * @param floorNum
	 * @param liftManager
	 */
	public Building(int liftNum, int liftCapacity, int floorNum, LiftManager liftManager, CsvManager manager, Clock clock) {
		this.csvManager=manager;
		this.clock=clock;
		//リフトの初期化
		lifts = new ArrayList<Lift>(liftNum);
		for(int i=0;i<liftNum;i++){
			lifts.add(new Lift(liftCapacity, 1, i+1,csvManager,clock));
		}
		//階数の初期化 TODO 配列の番号と階数が違いますよ
		floors = new ArrayList<Floor>(floorNum);
		for(int i=0;i<floorNum;i++){
			floors.add(new Floor(i+1));
		}

		//windowを初期化
		window = new Window(this);

		//LiftManagerを初期化
		liftManager.setBuilding(this);
		this.liftManager = liftManager;
	}
	/**
	 * {@link LiftSimulator#update()}から呼び出される、ビルの状態を更新するメソッドです。<br>
	 * 更新内容は以下です。<br>
	 * 1.フロアのタスクを更新({@link #updateFloor()})<br>
	 * フロアごとのタスクが乗車可能になっているかをチェックします。<br>
	 * 2.リフトの状態の更新({@link #updateLift()})<br>
	 * 各リフトが目的を元に行動します。<br>
	 * 3.ウィンドウの更新({@link #updateWindow()})<br>
	 * 本題とは関係ありませんが、視覚的にリフトの動作をチェックするためのウィンドウです。<br>
	 * ※時間がなければ実装しません。
	 */
	public void updateStatus(){
		updateFloor();
		updateLift();
		updateWindow();
	}
	/**
	 * 各フロアの状態を変更します。
	 */
	public void updateFloor(){
		for(Floor floor : floors){
			//指定された時間に乗車可能なタスクをすべて乗車可能に変更。
			floor.updateTasks(getClock());
		}
	}
	/**
	 * リフトの状態を更新します。リフトの更新作業の手順は以下です。
	 * <pre>
	 * (各リフトにおいて)
	 * 	(現在の階が目的の階でないなら)
	 * 		1.lift.move();
	 * 	(現在の階が目的の階なら)
	 * 		(下ろす人間または載せる人間がいる場合)
	 * 			(エレベータが閉じていたら)
	 * 				1.lift.open();//タスクを下ろす
	 * 				2.lift.waitLift();//wait=1
	 * 			(エレベータが開いていたら)
	 * 				(wait=待つべき時間(通常5)でないなら)
	 * 					1.lift.waitLift();//wait++
	 * 				(wait:待つべき時間(通常5)なら)
	 * 					1.lift.close();//タスクを入れる
	 * 					2.liftManager.getAim(lift);//新たなタスクを受け取る
	 * 					3.lift.move();//次の目的の階へ
	 * 		(下ろす人間または載せる人間がいない場合)
	 * 			(wait=待つべき時間(n)でないなら)
	 * 					1.lift.waitLift();//wait++
	 * 				(wait:待つべき時間(n)なら)
	 * 					1.liftManager.getAim(lift);//新たなタスクを受け取る
	 * 					2.lift.move();//次の目的の階へ
	 * </pre>
	 */
	public void updateLift(){
		/*
		 * ここの分岐について、if/elseが重なってわかりにくかったので、もう一つコードを考えました。
		 * →同パッケージのquestion1.txt
		 * 通常ならどうすべきかお尋ねしたいです。
		 */

		//各リフトにおいて
		for(Lift lift : lifts){
			Aim aim = lift.getAim();
			//現在の階が目的の階でないなら
			if(lift.getCurrentFloor()!=aim.getAimFloor()){
				lift.move();//目的地に向って移動する
			}
			//現在の階が目的の階なら
			else{
				//下ろす人間または載せる人間がいる場合
				if(aim.getBoard().length!=0||aim.getDescend().length!=0){
					//リフトが閉じていたら
					if(!lift.isOpen()){
						lift.open();//リフトを開いて人を下ろす
						lift.waitLift();//目的で指定された時間(>=5)待機する
					}
					//リフトが開いていたら
					else{
						//wait=待つべき時間(通常5)でないなら
						if(lift.getWaitTime()!=aim.getWaitTime()){
							lift.waitLift();
						}
						//wait=待つべき時間(通常5)なら
						else{
							if((lift.getCurrentFloor()*10)%10==5)
								System.err.println("致命的なエラー：リフトが階の真ん中で人を下ろそうとしてます");
							lift.close(getFloor((int)(lift.getCurrentFloor())));
							lift.setAim(liftManager.getAim(lift));
							lift.move();//次の目的階へ
						}
					}
				}
				//下ろす人間または載せる人間がいない場合
				else{
					//wait=待つべき時間(n)でないなら
					if(lift.getWaitTime()!=aim.getWaitTime()){
						lift.waitLift();
					}
					//wait=待つべき時間(n)なら
					else{
						lift.setAim(liftManager.getAim(lift));
						lift.move();//次の目的階へ
					}
				}
			}
		}
	}
	/**
	 * 画面の状態を更新します。
	 */
	public void updateWindow(){
		window.updateModel(this);
	}

	/**
	 * {@link #Building(int, int, int, LiftManager)}では設定していない情報に関して初期化を行います。<br>
	 * 設定スべき項目の例として、{@link CsvManager}を初期化した後にこのオブジェクトにタスクをセットしてからでないと
	 * 設定できないリフトの最初の目的({@link})などが挙げられます。
	 */
	public void initStatus(){
		//リフトの最初の目的の設定
		for(Lift lift : lifts){
			lift.setAim(liftManager.getAim(lift));
		}
	}

	/**
	 * 各階にタスクをセットします。
	 */
	public void setTasks(ArrayList<Task> tasks){
		//リフトを順に回して、
		for(Task task : tasks){
			//タスクの階を取得
			int boardFloor = task.getBoard();
			//各フロアに格納していく
			floors.get(boardFloor-1).addTask(task);
		}

	}

	/**
	 * シミュレーションを終了すべきか判定するメソッドです。<br>
	 * 各フロアのタスクがすべて終了していて、
	 * かつリフト内にタスクがなければtrueを返します。
	 * @return タスクがすべて完了していればtrue
	 */
	public boolean isFinished() {
		boolean ret = true;
		for(Floor floor : floors){
			if(!ret)break;
			ret&=floor.isFinished();
		}
		for(Lift lift:lifts){
			if(!ret)break;
			ret&=lift.hasNoTask();
		}
		return ret;
	}

	/**
	 * シミュレーターの時間情報を返します。<br>
	 * 静的メソッドとしての取得も可能ですが、
	 * 今後の拡張(ビルごとに時間が異なる場合と同じ場合)も考えて委譲的なメソッドを用意しています。
	 * フィールドに入れない理由はただの自分のオブジェクト構成のイメージです。
	 * @return
	 */
	public Clock getClock(){
		return clock;
	}
	/**
	 * シミュレーターの入出力を扱うオブジェクトを返します。<br>
	 * 静的メソッドとしての取得も可能ですが、
	 * 今後の拡張(ビルごとに入出力が異なる場合と同じ場合)も考えて委譲的なメソッドを用意しています。
	 * フィールドに入れない理由はただの自分のオブジェクト構成のイメージです。
	 * @return
	 */
	public CsvManager getCsvManager(){
		return csvManager;
	}
	public ArrayList<Lift> getLifts() {
		return lifts;
	}
	/**
	 * フロアを返します。
	 * 引数には直接階数を指定して下さい。
	 * -1する必要はありません。
	 * @param floor
	 * @return
	 */
	public Floor getFloor(int floor) {
		return floors.get(floor-1);
	}
	/**
	 * フロアの数を返します。
	 * 10階建てなら10が返ります。
	 * @return
	 */
	public int getFloorSize() {
		return floors.size();
	}
	/**
	 * タスクのIDによってタスクを返します。
	 * 第二引数にtrueを指定した場合、すでに搭乗可能である場合のみ返却されます。
	 * @param id 取得したいタスクのID
	 * @return 指定されたIDのタスク。無ければnull
	 */
	public Task getTaskByID(int id, boolean onlyCantRide){
		for(Floor floor : floors){
			Task ret = floor.getTaskByID(id,onlyCantRide);
			if(ret!=null)
				return ret;
		}
		return null;
	}
}
